﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.IO;
using System.Diagnostics;

namespace LOS.SP.ImportFiles
{
    class Program
    {
        private static string strSource = string.Empty;
        private static string strDestination = string.Empty;
        
        private static bool blnRecursive = false;
        private static bool blnReplaceName = false;
        private static bool blnDebug = false;

        private static int intTotalFiles = 0;
        private static int intUploadedFiles = 0;
        private static int intNameChanges = 0;
        private static int intErrors = 0;

        static void Main(string[] args)
        {
            string strFolderPath = string.Empty;

            if (args.Length > 0) //## Make sure the args array has something in it.
            {
                GetArgs(args);

                if (blnDebug)
                {
                    WriteDebug("------------- Starting debug at " + DateTime.Now.ToString() + " ------------");
                    WriteDebug("Source: " + strSource);
                    WriteDebug("Destination: " + strDestination);
                    WriteDebug("Recursive: " + blnRecursive.ToString());
                    WriteDebug("WebSafe: " + blnReplaceName.ToString());
                }

                 if (strSource == string.Empty && strDestination == string.Empty)
                {
                    Console.WriteLine("The source (-source) and destination (-destination) paths must be supplied.");
                }
                else
                {
                    try
                    {
                        if (blnDebug)
                            WriteDebug("Begin Processing File Uploads");

                        ProcessFileUploads(strSource, strDestination);

                        if (blnDebug)
                            WriteDebug("Finished Processing file uploads");

                        DirectoryInfo dirInfo = new DirectoryInfo(strSource);

                        if (blnRecursive == true && FolderContainsOtherFolders(dirInfo))
                        {
                            if (blnDebug)
                                WriteDebug("Begin processing recursive folders");

                            using (SPWeb site = new SPSite(strDestination).OpenWeb())
                            {
                                try
                                {
                                    if (ValidFolderPath(strDestination, site, ref strFolderPath))
                                    {
                                        if (blnDebug)
                                            WriteDebug("Begin building and populating structure in SharePoint");

                                        SPFolder folder = site.GetFolder(strFolderPath);
                                        BuildAndPopulateTree(strSource, folder);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    site.Dispose();
                                    Console.WriteLine("ERROR: " + ex.Message);
                                    intErrors++;
                                }
                            }
                        }


                    }
                    catch (Exception ex)
                    {

                        Console.WriteLine("ERROR: " + ex.Message);
                        intErrors++;
                    }
                    finally
                    {
                        Console.WriteLine("---------------------------------------------------------------");
                        Console.WriteLine("Total Files Processed: {0}", intTotalFiles);
                        Console.WriteLine("Total Files Uploaded: {0}", intUploadedFiles);
                        Console.WriteLine("Total Name Changes: {0}", intNameChanges);
                        Console.WriteLine("Total Errors: {0}", intErrors);
                    }
                }
            }
            else
            {
                Console.WriteLine("No Args");
                WriteHelp();
            }

        }

        private static void GetArgs(string[] args)
        {
            //## Note: need further testing on this to make sure it can't be broken.

            //## Use a for statement instead of a foreach, because we want to have an easy way to pull back the array item we are currently on while looping through the array.
            for (int i = 0; i < args.Length; i++)
            {
                //## Find out which "switch" we are on in the args array.
                switch (args[i])
                {
                    case "-source":
                        i++; //## We want the value (next arg) of this switch, so increase "i" by 1 before we grab the array value.                            
                        strSource = args[i];
                        break;
                    case "-destination":
                        i++; //## We want the value (next arg) of this switch, so increase "i" by 1 before we grab the array value.   
                        strDestination = Uri.UnescapeDataString(args[i]);
                        break;
                    case "-recursive":
                        blnRecursive = true;
                        break;
                    case "-replace":
                        blnReplaceName = true;
                        break;
                    case "-debug":
                        blnDebug = true;
                        break;
                    case "-?":
                        WriteHelp();
                        break;
                    case "/?":
                        WriteHelp();
                        break;
                    default:
                        break;
                }
            }
        }

        private static void WriteHelp()
        {
            Console.WriteLine("A utility used for moving files from a UNC path, to a SharePoint document library.");
            Console.WriteLine("-source       The directory path for the source files.");
            Console.WriteLine("-destination  The SharePoint URL where the files will be imported to.");
            Console.WriteLine("[-recursive]  Import subfolders and files also.");
            Console.WriteLine("[-replace]    Replace any non-safe characters with an underscore.");
            Console.WriteLine("[-debug]      Writes debug information to a file called debug.txt, located the same place the .exe is at.");
            Console.WriteLine("-?            Help.");
        }

        private static void BuildAndPopulateTree(string sourcePath, SPFolder parentFolder)
        {
            DirectoryInfo dirInfo = new DirectoryInfo(sourcePath);

            if (FolderContainsOtherFolders(dirInfo))
            {
                List<DirectoryInfo> directories = GetSubfolders(sourcePath);

                //## Run through each subfolder
                foreach (DirectoryInfo d in directories)
                {
                    //## Make the name "web safe"
                    string strFolderName = d.Name;
                    if (blnReplaceName)
                        strFolderName = MakeNameWebSafe(d.Name);

                    //## Create the folder within the parent folder on the SharePoint site
                    SPFolder newFolder = CreateSubFolder(parentFolder, strFolderName);

                    //## Upload all files that need to be in the newly created subfolder
                    ProcessFileUploads(d.FullName, newFolder.ParentFolder.ParentWeb.Url + "/" + newFolder.Url);

                    //## If this subfolder has child folders, then call this method again so we can process all of those.
                    if (FolderContainsOtherFolders(d))
                    {
                        BuildAndPopulateTree(d.FullName.ToString(), newFolder);
                    }

                }

            }

        }

        private static List<string> GetFileNames(string path)
        {
            List<string> files = new List<string>();

            //## Get all of the file names from the source path.
            if (Directory.Exists(path))
            {
                //## Add each file name into the names list
                foreach (string file in Directory.GetFiles(path))
                {
                    files.Add(file);
                }
            }

            return files;
        }

        private static List<DirectoryInfo> GetSubfolders(string path)
        {
            List<DirectoryInfo> directories = new List<DirectoryInfo>();

            if (Directory.Exists(path))
            {
                DirectoryInfo dirInfo = new DirectoryInfo(path);

                foreach (DirectoryInfo d in dirInfo.GetDirectories())
                {
                    directories.Add(d);
                }
            }

            return directories;
        }

        private static string MakeNameWebSafe(string ItemName)
        {
            bool blnChangeMade = false;

            //## Here is the list of unsafe characters
            List<char> InvalidChars = new List<char>();
            InvalidChars.Add('#');
            InvalidChars.Add('%');
            InvalidChars.Add('&');
            InvalidChars.Add('*');
            InvalidChars.Add(':');
            InvalidChars.Add('<');
            InvalidChars.Add('>');
            InvalidChars.Add('?');
            InvalidChars.Add('/');
            InvalidChars.Add('{');
            InvalidChars.Add('}');
            InvalidChars.Add('|');
            InvalidChars.Add('=');
            InvalidChars.Add('+');
            InvalidChars.Add(',');
            InvalidChars.Add('@');
            InvalidChars.Add('~');

            StringBuilder sb = new StringBuilder();

            //## Check each character to make sure it is "safe"
            foreach (char c in ItemName)
            {
                if (InvalidChars.Contains(c))
                {
                    sb.Append("_");
                    blnChangeMade = true;
                }
                else
                {
                    sb.Append(c);
                }
            }

            string NewName = sb.ToString().Trim();

            //## Replace any double periods with a single period.
            if (NewName.Contains(".."))
            {
                NewName = NewName.Replace("..", ".");
                blnChangeMade = true;
            }


            if (blnChangeMade)
            {
                Console.WriteLine("Changing name: {0} to {1}", ItemName, NewName);
                intNameChanges++;
            }

            //## Return the new name, but trim it to remove any whitespace from the ends. Whitespace is not allowed
            return NewName;

        }

        private static void ProcessFileUploads(string sourcePath, string destinationPath)
        {     
            //## Get the file names for the current node, identified by the node's id attribute
            List<string> names = GetFileNames(sourcePath);

            //## Run through the names list we just populated, and upload each file.
            foreach (string fPath in names)
            {
                if (blnDebug)
                    WriteDebug("Start uploading file: " + fPath); 
                UploadFile(fPath, destinationPath);
            }
        }

        private static void UploadFile(string srcUrl, string destURL)
        {
            intTotalFiles++;

            try
            {
                //## make sure file exists on source machine
                if (!File.Exists(srcUrl))
                {
                    if (blnDebug)
                        WriteDebug("ERROR: " + srcUrl + " does not exist");

                    intErrors++;
                    return;
                }

                //## Add the file name to the destination path. Check to see if "/" needs to be added between the two (the user could have added it).
                if (destURL.LastIndexOf(@"/") == destURL.Length)
                {
                    destURL += srcUrl.Substring(srcUrl.LastIndexOf(@"\") + 1);
                }
                else
                {
                    destURL += "/" + srcUrl.Substring(srcUrl.LastIndexOf(@"\") + 1);
                }

                using (SPWeb site = new SPSite(destURL).OpenWeb())
                {
                    //## Put the file into a filestream
                    FileStream fStream = File.OpenRead(srcUrl);
                    byte[] contents = new byte[fStream.Length];
                    fStream.Read(contents, 0, (int)fStream.Length);
                    fStream.Close();

                    //## Perform a "Replace Name" if needed.
                    string strNewDestUrl = destURL;
                    if (blnReplaceName)
                    {
                        string oldFileName = destURL.Substring(destURL.LastIndexOf("/") + 1);
                        string newFileName = MakeNameWebSafe(oldFileName);
                        strNewDestUrl = destURL.Replace(oldFileName, newFileName);

                        if (blnDebug)
                            WriteDebug("Changed filename to " + newFileName);
                    }

                    if (blnDebug)
                        WriteDebug("Ensure parent folder");
                    EnsureParentFolder(site, strNewDestUrl);

                    if (blnDebug)
                        WriteDebug("Adding file: " + srcUrl);
                    site.Files.Add(strNewDestUrl, contents, true);

                    intUploadedFiles++;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR: " + ex.Message);
                intErrors++;
            }

        }

        private static bool FolderContainsOtherFolders(SPFolder folder)
        {
            if (folder.SubFolders.Count > 0)
                return true;

            return false;
        }

        private static bool FolderContainsOtherFolders(DirectoryInfo dirInfo)
        {
            if (dirInfo.GetDirectories().Length > 0)
                return true;

            return false;
        }

        private static SPFolder CreateSubFolder(SPFolder folder, string newFolder)
        {
            if (blnDebug)
                WriteDebug("Create subfolder: " + newFolder + " in " + folder.Name.ToString());

            return folder.SubFolders.Add(newFolder);
        }

        private static string EnsureParentFolder(SPWeb parentSite, string destUrl)
        {
            destUrl = parentSite.GetFile(destUrl).Url;

            int index = destUrl.LastIndexOf("/");
            string parentFolderUrl = string.Empty;

            if (index > -1)
            {
                parentFolderUrl = destUrl.Substring(0, index);

                SPFolder parentFolder = parentSite.GetFolder(parentFolderUrl);

                if (!parentFolder.Exists)
                {
                    if (blnDebug)
                        WriteDebug("Folder does not exist so creating it in SharePoint");

                    SPFolder currentFolder = parentSite.RootFolder;

                    foreach (string folder in parentFolderUrl.Split('/'))
                    {
                        currentFolder = currentFolder.SubFolders.Add(folder);
                    }
                }
            }
            return parentFolderUrl;
        }

        private static string CleanUriString(string strUri)
        {
            //## Remove the extra junk from the URL string that we will be using
            if (strUri.Contains(@"/Forms/AllItems.aspx"))
                return strUri.Remove(strUri.IndexOf(@"/Forms/AllItems.aspx"));
            if (strUri.LastIndexOf(@"/") == strUri.Length - 1)
                return strUri.Remove(strUri.LastIndexOf(@"/"));

            return strUri;
        }

        private static bool ValidFolderPath(string _strSitePath, SPWeb web, ref string strFolders)
        {
            bool blnValidFolderPath = false;

            strFolders = CleanUriString(_strSitePath);

            //## Check to see if the URL string we are using is longer than the web.url.
            //## If it isn't, then the user forgot to put in a folder name which is required.
            if (strFolders.Length > web.Url.Length)
            {
                //## Remove the site from the string so we are only left with the folders.
                strFolders = strFolders.Substring(web.Url.Length + 1);

                //## Put each folder name into an array
                string[] folders = strFolders.Split(new Char[] { '/' });

                //## Get the document library
                string strRootLibrary = System.Uri.UnescapeDataString(folders[0]);

                if (LibraryExists(strRootLibrary, web))
                {
                    blnValidFolderPath = true;
                    SPFolder spfolder = web.Folders[strRootLibrary];

                    for (int i = 1; i < folders.Length; i++)
                    {
                        //## Get each subfolder within the library. We start at "1" becuase arrays are zero based, and zero is the library.
                        if (FolderExists(folders[i], spfolder, web))
                        {
                            blnValidFolderPath = true;
                            spfolder = spfolder.SubFolders[folders[i]];
                        }
                        else
                        {
                            blnValidFolderPath = false;
                        }
                    }
                }
                else
                {
                    blnValidFolderPath = false;
                }
            }
            else
            {
                blnValidFolderPath = false;
            }

            if (blnDebug)
                WriteDebug("Valid folder path for " + strFolders + ": " + blnValidFolderPath.ToString());

            return blnValidFolderPath;
        }

        private static bool LibraryExists(string strLibrary, SPWeb web)
        {
            bool blnExists = false;

            using (web)
            {
                try
                {
                    //## Check if the library exists on the site.
                    if (web.Folders[strLibrary].Exists)
                        blnExists = true;
                }
                catch //## I could have looped through each library and found a name match, but this is much easier.
                {
                    //## Make sure the web object is disposed of since it will not complete the using statement in a catch block
                    web.Dispose();
                    blnExists = false;
                }

            }

            if (blnDebug)
                WriteDebug("Library Exists: " + blnExists.ToString());

            return blnExists;
        }

        private static bool FolderExists(string strFolder, SPFolder parentFolder, SPWeb web)
        {
            bool blnExists = false;

            using (web)
            {
                //## Check to see if there are child folders. If there are none, we know the subfolder does not exist so no reason to go further.
                if (parentFolder.SubFolders.Count > 0)
                {
                    try
                    {
                        //## Check if the folder exists.
                        if (parentFolder.SubFolders[strFolder].Exists)
                            blnExists = true;
                    }
                    catch //## I could have looped through each folder and found a name match, but this is much easier.
                    {
                        //## Make sure the web object is disposed of since it will not complete the using statement in a catch block
                        web.Dispose();
                        return false;
                    }
                }

            }

            if (blnDebug)
                WriteDebug("Folder '" + strFolder + "' in '" + parentFolder.Name.ToString() + "' exists: " + blnExists);

            return blnExists;
        }

        private static void WriteDebug(string Message)
        {
            using (StreamWriter sw = new StreamWriter("debug.txt", true))
            {
                sw.WriteLine(Message);
            }
        }
    }
}
